#[allow(dead_code)]
use std::os::raw::*;
use std::str::FromStr;

pub const HTTP_METHOD_GET: i32 = 0;
pub const HTTP_METHOD_POST: i32 = 1;
pub const HTTP_METHOD_PUT: i32 = 2;

#[repr(C)]
pub struct Doggo {
    pub many: ::std::os::raw::c_int,
    pub wow: ::std::os::raw::c_char,
}

#[repr(C,packed)]
pub struct THTTPHeader {
    pub name: *const c_char,
    pub value: *const c_char,
}

#[repr(C, packed)]
pub struct THTTPResponse {
    pub status_code: c_int,
    pub user_data: c_longlong,
    pub error_msg: *const c_char,
    pub data: *const c_char,
    pub size: c_int,
}
#[repr(C, packed)]
pub struct THTTPRequest {
    pub url: *const c_char,
    pub method: c_int, // 0=get 1=post 2=put
    pub headers: [THTTPHeader; 128usize],
    pub data: *const c_char,
    pub size: c_int,
    pub user_data: c_longlong,
    pub callback: extern "C" fn(resp: *const THTTPResponse),
    pub connect_timeout: u64,
    pub timeout: u64,
}

use libc::strlen;
use std::{slice, str};

fn collect_headers(req: &THTTPRequest) -> Result<reqwest::header::HeaderMap, String> {
    let mut headers = reqwest::header::HeaderMap::new();
    for h in req.headers.iter() {
        if h.name == std::ptr::null() {
            break;
        }
        let name = unsafe {
            str::from_utf8_unchecked(slice::from_raw_parts(h.name as *const u8, strlen(h.name)))
        }
        .to_owned();
        let value = unsafe {
            str::from_utf8_unchecked(slice::from_raw_parts(h.value as *const u8, strlen(h.value)))
        }
        .to_owned();

        if let Ok(header_def) = reqwest::header::HeaderName::from_str(&name) {
            headers.append(header_def, HeaderValue::from_str(&value).unwrap());
        } else {
            return Err(format!("invalid header name!{}", name));
        }
    }
    Ok(headers)
}

async fn request(
    url: String,
    method: i32,
    headers: &reqwest::header::HeaderMap,
    content: Vec<u8>,
    connect_timeout: u64,
    timeout: u64,
) -> std::result::Result<(i32, String), String> {
    let method = match method {
        HTTP_METHOD_POST => reqwest::Method::POST,
        HTTP_METHOD_PUT => reqwest::Method::PUT,
        HTTP_METHOD_GET => reqwest::Method::GET,
        _ => reqwest::Method::GET,
    };
    let client: reqwest::Client = reqwest::Client::builder()
        .connect_timeout(std::time::Duration::from_millis(if connect_timeout > 0 {
            connect_timeout
        } else {
            5000u64
        }))
        .timeout(std::time::Duration::from_millis(if timeout > 0 {
            timeout
        } else {
            8000u64
        }))
        .build()
        .map_err(|e| format!("{}", e))?;
    let mut builder: reqwest::RequestBuilder = client.request(method, &url);
    if content.len() > 0 {
        builder = builder.body(content);
    }
    for hdr in headers.iter() {
        builder = builder.header(hdr.0, hdr.1);
    }
    let resp = builder.send().await.map_err(|e| format!("{}", e))?;
    let status_code = resp.status().as_u16() as i32;
    let data = resp.text().await.map_err(|e| format!("{}", e))?;

    Ok((status_code, data))
}

fn fire_callback(
    callback: Box<extern "C" fn(*const THTTPResponse)>,
    user_data: i64,
    result: Result<(i32, String), String>,
) {
    match result {
        Ok((code, body)) => {
            let mut res: THTTPResponse = unsafe { std::mem::zeroed() };
            let buf = body.into_bytes();
            res.user_data = user_data;
            res.status_code = code;
            res.size = buf.len() as c_int;
            res.data = buf.as_ptr() as *const i8;
            callback(&res as *const THTTPResponse);
        }
        Err(error) => {
            let mut res: THTTPResponse = unsafe { std::mem::zeroed() };
            res.status_code = -1;
            res.user_data = user_data;
            res.error_msg = error.as_ptr() as *const c_char;
            callback(&res as *const THTTPResponse);
        }
    }
}

use lazy_static::lazy_static;
use reqwest::header::HeaderValue;
use tokio::runtime;
lazy_static! {
    static ref G_RUNTIME: std::sync::Mutex<tokio::runtime::Runtime> = {
        std::sync::Mutex::new(
            runtime::Builder::new()
                .threaded_scheduler()
                .core_threads(3)
                .enable_all()
                .build()
                .unwrap(),
        )
    };
}

#[no_mangle]
pub extern "C" fn http_request(r: *const THTTPRequest) {
    let req: &THTTPRequest = unsafe { &*r };
    let user_data = req.user_data;
    let method = req.method;
    let callback = Box::new(req.callback);
    let timeout = req.timeout;
    let connect_timeout = req.connect_timeout;
    let url = unsafe {
        str::from_utf8_unchecked(slice::from_raw_parts(req.url as *const u8, strlen(req.url)))
    }
    .to_owned();
    let content =
        unsafe { slice::from_raw_parts(req.data as *const u8, req.size as usize) }.to_vec();
    match collect_headers(&req) {
        Ok(headers) => {
            G_RUNTIME.lock().unwrap().spawn(async move {
                let resp = request(url, method, &headers, content, connect_timeout, timeout).await;
                fire_callback(callback, user_data, resp);
            });
        }
        Err(err) => {
            fire_callback(callback, user_data, Err(err));
        }
    }
}

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::net::SocketAddr;
use std::sync::Arc;

type GenericError = Box<dyn std::error::Error + Send + Sync>;
type ServerResult<T> = std::result::Result<T, GenericError>;

#[repr(C, packed)]
pub struct THTTPServerRequest {
    pub url: *const c_char,
    pub resp_sender: *const c_void,
    pub method: *const c_char,
    pub data: *const c_char,
    pub size: c_int,
    pub user_data: u64,
    pub headers: [THTTPHeader; 128usize],
}

#[repr(C, packed)]
pub struct THTTPServerResponse {
    pub sender: *mut c_void, //tokio::sync::oneshot::Sender<Response<Body>>
    pub status: i32,
    pub data: *const c_char,
    pub size: c_int,
    pub headers: [THTTPHeader; 128usize],
}

#[repr(C, packed)]
pub struct THTTPServerOptions {
    pub user_data: u64,
    pub handler: extern "C" fn(req: *const THTTPServerRequest),
    pub addr: *const c_char,
}

pub struct THttpServer {
    close_signal: Option<tokio::sync::oneshot::Sender<()>>,
}

pub struct RequestHandler {}

async fn response_handler(
    req: Request<Body>,
    callback: Arc<extern "C" fn(req: *const THTTPServerRequest)>,
    _user_data: u64,
) -> ServerResult<Response<Body>> {
    let method = req.method().as_str().to_owned();
    let url = req.uri().path().to_owned();
    let headers: Vec<(String, String)> = req
        .headers()
        .iter()
        .map(|h| (h.0.as_str().to_owned(), h.0.as_str().to_owned()))
        .collect();
    let whole_body = hyper::body::to_bytes(req.into_body()).await?;

    let (tx, rx) = tokio::sync::oneshot::channel::<Response<Body>>();
    {
        let mut req_ffi = THTTPServerRequest {
            url: url.as_ptr() as *const c_char,
            resp_sender: Box::into_raw(Box::new(tx)) as *const c_void,
            method: method.as_ptr() as *const c_char,
            data: whole_body.as_ptr() as *const c_char,
            size: whole_body.len() as c_int,
            user_data: _user_data,
            headers: unsafe { std::mem::zeroed() },
        };
        let mut num = 0;
        for hdr in headers.iter() {
            req_ffi.headers[num].name = hdr.0.as_ptr() as *const c_char;
            req_ffi.headers[num].value = hdr.1.as_ptr() as *const c_char;
            num += 1;
        }
        callback(&req_ffi);
    }
    let resp = rx.await?;
    Ok(resp)
}

#[no_mangle]
pub extern "C" fn http_server_start(_options: *const THTTPServerOptions) -> *mut THttpServer {
    let options: &THTTPServerOptions = unsafe { &*_options };
    let (tx, rx) = tokio::sync::oneshot::channel::<()>();
    let addr_str = unsafe {
        str::from_utf8_unchecked(slice::from_raw_parts(
            options.addr as *const u8,
            strlen(options.addr),
        ))
    };
    let addr: SocketAddr = addr_str.parse().expect("Unable to parse socket address");
    let user_data = options.user_data;
    let callback = Arc::new(options.handler);
    let new_service = make_service_fn(move |_| {
        let cb = callback.clone();
        async move {
            Ok::<_, GenericError>(service_fn(move |req| {
                // Clone again to ensure that client outlives this closure.
                response_handler(req, cb.clone(), user_data)
            }))
        }
    });
    G_RUNTIME.lock().unwrap().spawn(async move {
        Server::bind(&addr)
            .serve(new_service)
            .with_graceful_shutdown(async {
                rx.await.ok();
            })
            .await
    });

    let boxed = Box::new(THttpServer {
        close_signal: Some(tx),
    });
    Box::into_raw(boxed)
}

#[no_mangle]
pub extern "C" fn http_server_free(boxed: *mut THttpServer) -> bool {
    if boxed.is_null() {
        return false;
    }
    let mut info = unsafe { Box::from_raw(boxed) };
    if let Some(signal) = info.close_signal.take() {
        let _ = signal.send(());
    }
    return false;
}

pub fn http_server_response(_res: *mut THTTPServerResponse) {
    assert!(!_res.is_null());
    let res: &THTTPServerResponse = unsafe { &*_res };
    let sender = unsafe { Box::from_raw(res.sender as *mut tokio::sync::oneshot::Sender<Response<Body>>) };
    let status = hyper::StatusCode::from_u16(res.status as u16).unwrap();

    let mut response = Response::default();
    *response.status_mut() = status;

    for h in res.headers.iter() {
        if h.name == std::ptr::null() {
            break;
        }
        let name = unsafe {
            str::from_utf8_unchecked(slice::from_raw_parts(h.name as *const u8, strlen(h.name)))
        }
            .to_owned();
        let value = unsafe {
            str::from_utf8_unchecked(slice::from_raw_parts(h.value as *const u8, strlen(h.value)))
        }
            .to_owned();
        if let Ok(header_def) = reqwest::header::HeaderName::from_str(&name) {
            response
                .headers_mut()
                .append(header_def, HeaderValue::from_str(&value).unwrap());
        }
    }
    *response.body_mut() =
        Body::from(unsafe { slice::from_raw_parts(res.data as *const u8, res.size as usize) });
    let _ = sender.send(response);
}
